package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "os"
    "os/exec"
    "path/filepath"
    "runtime"
    "strings"
    "sync"
    "syscall"
    "time"
    "github.com/skip2/go-qrcode"
)

const maxUploadSize = 1 << 30 // 1GB

type ChatMsg struct {
    Time    string
    Type    string // "text" or "file"
    Content string // 文本内容或文件名
    FileID  string // 文件唯一标识
    Sender  string // "pc" 或 "phone"
}

var (
    chatMemory []ChatMsg
    chatLock   sync.Mutex
    fileStore  = make(map[string]string) // fileID -> 路径
)

// 自动打开浏览器
func openBrowser(url string) {
    var cmd string
    var args []string
    switch runtime.GOOS {
    case "windows":
        cmd = "rundll32"
        args = []string{"url.dll,FileProtocolHandler", url}
    case "darwin":
        cmd = "open"
        args = []string{url}
    default:
        cmd = "xdg-open"
        args = []string{url}
    }
    exec.Command(cmd, args...).Start()
}

// Windows下最小化控制台窗口
func minimizeConsoleWindow() {
    if runtime.GOOS != "windows" {
        return
    }
    kernel32 := syscall.NewLazyDLL("kernel32.dll")
    user32 := syscall.NewLazyDLL("user32.dll")
    getConsoleWindow := kernel32.NewProc("GetConsoleWindow")
    showWindow := user32.NewProc("ShowWindow")
    hwnd, _, _ := getConsoleWindow.Call()
    if hwnd != 0 {
        showWindow.Call(hwnd, 2) // SW_MINIMIZE = 2
    }
}

func main() {
    // Windows下启动后自动最小化
    go func() {
        if runtime.GOOS == "windows" {
            time.Sleep(500 * time.Millisecond)
            minimizeConsoleWindow()
        }
    }()

    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/chat", chatHandler)
    http.HandleFunc("/chatlog", chatLogHandler)
    http.HandleFunc("/file/", fileDownloadHandler)

    ips := getAllUsableIPs()
    fmt.Println("请用手机扫码网页上的二维码，或用以下任意地址访问：")
    for _, ip := range ips {
        fmt.Printf("  http://%s\n", ip)
    }

    // 自动打开浏览器，使用第一个可用IP
    go func() {
        url := fmt.Sprintf("http://%s", ips[0])
        time.Sleep(800 * time.Millisecond)
        openBrowser(url)
    }()

    log.Fatal(http.ListenAndServe("0.0.0.0:80", nil))
}

// 网卡过滤、IP获取
func isVirtualInterface(name string) bool {
    name = strings.ToLower(name)
    virtualPrefixes := []string{
        "veth", "docker", "vmnet", "vboxnet", "virbr", "npf_", "hyper-v", "tap", "tun", "lo",
        "loopback", "npcap", "br-", "wg", "vmware", "virtual",
    }
    for _, prefix := range virtualPrefixes {
        if strings.HasPrefix(name, prefix) || strings.Contains(name, prefix) {
            return true
        }
    }
    return false
}

func getAllUsableIPs() []string {
    var ips []string
    ifas, err := net.Interfaces()
    if err != nil {
        return []string{"localhost"}
    }
    for _, ifa := range ifas {
        if ifa.Flags&net.FlagUp == 0 || ifa.Flags&net.FlagLoopback != 0 {
            continue
        }
        if isVirtualInterface(ifa.Name) {
            continue
        }
        addrs, err := ifa.Addrs()
        if err != nil {
            continue
        }
        for _, addr := range addrs {
            var ip net.IP
            switch v := addr.(type) {
            case *net.IPNet:
                ip = v.IP
            case *net.IPAddr:
                ip = v.IP
            }
            if ip == nil || ip.IsLoopback() {
                continue
            }
            ip = ip.To4()
            if ip == nil {
                continue
            }
            ips = append(ips, ip.String())
        }
    }
    if len(ips) == 0 {
        return []string{"localhost"}
    }
    return ips
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    ips := getAllUsableIPs()
    url := fmt.Sprintf("http://%s", ips[0])
    png, _ := qrcode.Encode(url, qrcode.Medium, 200)
    b64 := base64.StdEncoding.EncodeToString(png)

    var urlListHTML strings.Builder
    urlListHTML.WriteString(`<div style="font-size:15px;word-break:break-all;">`)
    for _, ip := range ips {
        urlListHTML.WriteString(fmt.Sprintf(`<div><a href="http://%s" target="_blank">http://%s</a></div>`, ip, ip))
    }
    urlListHTML.WriteString(`</div>`)

    html := `
    <!DOCTYPE html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta charset="utf-8">
    <title>手机&电脑互传文件/聊天</title>
    <style>
    html, body { margin: 0; padding: 0; height: 100%; background: #f5f6fa; font-family: "微软雅黑", Arial, sans-serif; font-size: 17px; box-sizing: border-box; }
    .mainbox { display: flex; min-height: 100vh; }
    .chat-side { flex: 1; min-width:0; display: flex; flex-direction: column; background: #fff; box-shadow: 0 0 8px #0001; margin: 0 0 0 0; }
    .right-part { width: 320px; min-width:180px; max-width: 98vw; box-shadow: 0 0 10px #0001; background: #fafbfc; padding: 30px 10px 18px 10px;}
    .qrcode-float { text-align: center; }
    .qrcode-float img { width: 140px; max-width: 38vw; min-width: 60px; }
    .qrcode-url { font-size: 0.98em; word-break: break-all; margin: 7px 0 0 0; }
    .chat-title { font-weight: bold; font-size: 22px; margin: 20px 0 10px 0; text-align:center;}
    .chatlog { flex: 1; margin: 0 0 8px 0; padding: 12px 10px 12px 10px; border: 1px solid #eee; border-radius: 7px; background: #f8fafc; height: 320px; overflow-y: auto; font-size: 1em;}
    .chat-form { margin: 0; padding: 4px; background: #fff; border-radius: 6px; box-shadow: 0 2px 10px #0001; }
    .chat-form textarea { width: 95%; height: 38px; font-size: 1em; resize: vertical; margin-bottom: 4px;}
    .chat-form input[type="file"] { margin:0 0 8px 0;}
    .chat-form button { margin-top: 6px; font-size: 1em; padding: 0.38em 1.8em; }
    .chat-msg-wrap { display: flex; align-items: center; margin-bottom: 7px;}
    .chat-msg-content { flex: 1; word-break: break-all; font-size: 1em;}
    .copy-btn { margin-left: 8px; padding: 0 10px; height: 24px; font-size: 13px; border: 1px solid #ccc; background: #f4f4f4; border-radius: 3px; cursor: pointer;}
    .copy-btn:active { background: #e0e0e0; }
    .time { color: #999; font-size: 12px; margin-right: 4px;}
    .sender-phone { color: #007aff; font-size: 13px; margin-right: 5px; }
    .sender-pc { color: #2e86de; font-size: 13px; margin-right: 5px; }
    @media (max-width:900px){ .right-part{ display:none;} .mainbox{flex-direction:column;}}
    @media (max-width:600px){ html,body{ font-size:15px; } .chatlog{height:150px;}}
    </style>
    <script>
    function loadChat(){
        fetch('/chatlog')
        .then(r=>r.text())
        .then(txt=>{
            let chatlog = document.getElementById('chatlog');
            chatlog.innerHTML = txt;
            addCopyEvents();
            chatlog.scrollTop = chatlog.scrollHeight;
        });
    }
    function addCopyEvents() {
        document.querySelectorAll('.copy-btn').forEach(function(btn){
            btn.onclick = function(){
                let msg = this.getAttribute('data-msg');
                if (navigator.clipboard) {
                    navigator.clipboard.writeText(msg);
                } else {
                    let textarea = document.createElement('textarea');
                    textarea.value = msg;
                    document.body.appendChild(textarea);
                    textarea.select();
                    document.execCommand('copy');
                    document.body.removeChild(textarea);
                }
                this.innerText = '已复制';
                setTimeout(()=>{this.innerText='复制'}, 1000);
            }
        });
    }
    // 支持粘贴或拖拽文件到聊天窗口自动上传
    window.addEventListener('DOMContentLoaded', function() {
        var chatForm = document.querySelector('.chat-form');
        var textarea = chatForm.querySelector('textarea');
        var fileInput = chatForm.querySelector('input[type="file"]');
        // 拖拽
        chatForm.addEventListener('dragover', function(e) {
            e.preventDefault();
            chatForm.style.background = '#e8f0fe';
        });
        chatForm.addEventListener('dragleave', function(e) {
            e.preventDefault();
            chatForm.style.background = '';
        });
        chatForm.addEventListener('drop', function(e) {
            e.preventDefault();
            chatForm.style.background = '';
            if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
                fileInput.files = e.dataTransfer.files;
            }
        });
        // 粘贴
        chatForm.addEventListener('paste', function(e) {
            if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) {
                fileInput.files = e.clipboardData.files;
            }
        });
        // 回车发送
        textarea.addEventListener('keydown', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                chatForm.querySelector('button[type="submit"]').click();
            }
        });
    });
    setInterval(loadChat, 2000);
    window.onload = function() {
        loadChat();
    }
    </script>
    </head>
    <body>
    <div class="mainbox">
      <div class="chat-side">
        <div class="chat-title">手机 & 电脑 文件互传/聊天</div>
        <div class="chatlog" id="chatlog"></div>
        <form class="chat-form" action="/chat" method="post" enctype="multipart/form-data" onsubmit="setTimeout(loadChat, 200);">
            <textarea name="msg" maxlength="200" placeholder="输入消息...（回车发送，Shift+回车换行）"></textarea><br/>
            <input type="file" name="chatfile" style="margin:8px 0;">
            <button type="submit">发送</button>
        </form>
      </div>
      <div class="right-part">
        <div class="qrcode-float">
            <div><b>扫码访问</b></div>
            <img src="data:image/png;base64,` + b64 + `" alt="二维码"/><br>
            <div class="qrcode-url"><a href="` + url + `">` + url + `</a></div>
            <div style="margin-top:10px;"><b>本机可用地址：</b></div>
            ` + urlListHTML.String() + `
        </div>
      </div>
    </div>
    </body>
    </html>
    `
    w.Write([]byte(html))
}

func chatHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }
    msg := strings.TrimSpace(r.FormValue("msg"))
    ua := r.Header.Get("User-Agent")
    sender := "phone"
    if strings.Contains(strings.ToLower(ua), "windows") || strings.Contains(strings.ToLower(ua), "macintosh") || strings.Contains(strings.ToLower(ua), "linux") {
        sender = "pc"
    }
    var fileID string
    var fileName string

    r.ParseMultipartForm(16 << 20)
    file, handler, err := r.FormFile("chatfile")
    if err == nil && handler != nil && handler.Filename != "" {
        defer file.Close()
        ts := time.Now().UnixNano()
        fileID = fmt.Sprintf("%d_%s", ts, handler.Filename)
        savePath := filepath.Join(os.TempDir(), fileID)
        f, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE, 0666)
        if err == nil {
            io.Copy(f, file)
            f.Close()
            chatLock.Lock()
            fileStore[fileID] = savePath
            chatLock.Unlock()
            fileName = handler.Filename
        }
    }

    chatLock.Lock()
    defer chatLock.Unlock()
    now := time.Now().Format("15:04:05")
    if fileID != "" && fileName != "" {
        chatMemory = append(chatMemory, ChatMsg{
            Time: now, Type: "file", Content: fileName, FileID: fileID, Sender: sender,
        })
    }
    if msg != "" {
        chatMemory = append(chatMemory, ChatMsg{
            Time: now, Type: "text", Content: msg, Sender: sender,
        })
    }
    if len(chatMemory) > 100 {
        chatMemory = chatMemory[len(chatMemory)-100:]
    }
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

func chatLogHandler(w http.ResponseWriter, r *http.Request) {
    chatLock.Lock()
    defer chatLock.Unlock()
    var out strings.Builder
    for _, msg := range chatMemory {
        senderClass := "sender-phone"
        senderLabel := "手机"
        if msg.Sender == "pc" {
            senderClass = "sender-pc"
            senderLabel = "电脑"
        }
        switch msg.Type {
        case "text":
            safe := strings.ReplaceAll(msg.Content, "<", "&lt;")
            safe = strings.ReplaceAll(safe, "\"", "&quot;")
            safe = strings.ReplaceAll(safe, "'", "&#39;")
            out.WriteString(`<div class="chat-msg-wrap"><span class="time">[`)
            out.WriteString(msg.Time)
            out.WriteString("]</span><span class=\"" + senderClass + "\">" + senderLabel + "</span><span class=\"chat-msg-content\">")
            out.WriteString(safe)
            out.WriteString(`</span><button class="copy-btn" data-msg="`)
            out.WriteString(safe)
            out.WriteString(`">复制</button></div>`)
        case "file":
            fileLink := fmt.Sprintf(`<a href="/file/%s" target="_blank">%s</a>`, msg.FileID, msg.Content)
            out.WriteString(`<div class="chat-msg-wrap"><span class="time">[`)
            out.WriteString(msg.Time)
            out.WriteString("]</span><span class=\"" + senderClass + "\">" + senderLabel + "</span><span class=\"chat-msg-content\">")
            out.WriteString("[文件] " + fileLink)
            out.WriteString(`</span></div>`)
        }
    }
    w.Write([]byte(out.String()))
}

func fileDownloadHandler(w http.ResponseWriter, r *http.Request) {
    fileID := strings.TrimPrefix(r.URL.Path, "/file/")
    chatLock.Lock()
    savePath, ok := fileStore[fileID]
    chatLock.Unlock()
    if !ok || savePath == "" {
        http.NotFound(w, r)
        return
    }
    fname := ""
    idx := strings.Index(fileID, "_")
    if idx > 0 && len(fileID) > idx+1 {
        fname = fileID[idx+1:]
    } else {
        fname = fileID
    }
    w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fname))
    http.ServeFile(w, r, savePath)
}